#include "MapRendererWidget.h"
#include "GLProgressDialog.h"
#include <cmath>
#include <QDebug>
#include <QApplication>


#define RESOLVE_GL_FUNC(f) ok &= bool((f = (_gl##f) context()->getProcAddress(QLatin1String("gl" #f))));

#define CELL_SIZE 0.1
#define MAX_HEIGHT 12
#define MAX_SHORT 65535

MapRendererWidget::MapRendererWidget(QWidget* parent) : QGLWidget(parent) {
    xRot = 0;
    yRot = 0;
    zRot = 0;
    dx = 0;
    dy = 0;
    lastZoom = 1.0;
    compileObject = false;
    blockRerender = false;
    m_vertexes = 0;
    m_normals = 0;
    m_texCoords = 0;
    m_computedNormals = false;
    m_indexes = 0;
    getGLExtensionFunctions().resolve(context());
}

void MapRendererWidget::initializeGL() {
    GLfloat mat_ambient[] = { 0.8, 0.2, 0.2, 1.0 };
    GLfloat mat_specular[] = { 1.0, 0.8, 0.8, 0.0 };
    GLfloat mat_shininess[] = { 100.0 };
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_SMOOTH);

    //glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_ambient);
    //glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    //glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);

    //glEnable(GL_LIGHTING);
    //glEnable(GL_LIGHT0);
    //glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

void MapRendererWidget::resizeGL(int w, int h) {
    glViewport(0, 0, (GLint)w, (GLint)h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity();
    if (w <= h)
        glOrtho (dx+lastZoom*-100, dx+lastZoom*100,/*left,right*/
                 dy+lastZoom*-100*(GLfloat)h/(GLfloat)w, dy+lastZoom*100*(GLfloat)h/(GLfloat)w,/*top,bottom*/
                 -2000.0, 2000.0);/*near,far*/
    else
        glOrtho (dx+lastZoom*-100*(GLfloat)w/(GLfloat)h, dx+lastZoom*100*(GLfloat)w/(GLfloat)h,/*left,right*/
                 dy+lastZoom*-100, dy+lastZoom*100,/*top,bottom*/
                 -2000.0, 2000.0);/*near,far*/
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void MapRendererWidget::paintGL() {
    if(!m_heightmap.getWidth() || blockRerender) return;
    GLfloat light_position[] = { 5, 5, MAX_HEIGHT*1.3, 0.0 };
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    if(compileObject) {
        makeObject();
        compileObject = false;
    }
    glRotatef(-90, 0, 0, 1);
    glRotated(xRot / 16.0, 1.0, 0.0, 0.0);
    glRotated(yRot / 16.0, 0.0, 1.0, 0.0);
    glRotated(zRot / 16.0, 0.0, 0.0, 1.0);
    glTranslatef(-m_heightmap.getHeight()*CELL_SIZE/2., -m_heightmap.getWidth()*CELL_SIZE/2., 0);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glEnableClientState( GL_VERTEX_ARRAY );
    glEnableClientState( GL_TEXTURE_COORD_ARRAY );
    //glEnableClientState( GL_NORMAL_ARRAY );

    if(getGLExtensionFunctions().openGL15Supported()) {
        glBindBuffer(GL_ARRAY_BUFFER, m_VBOVertices);
        glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );

        //glBindBuffer(GL_ARRAY_BUFFER, m_VBONormals);
        //glNormalPointer(GL_FLOAT, 0, (char *) NULL );

        glBindBuffer( GL_ARRAY_BUFFER, m_VBOTexCoords );
        glTexCoordPointer( 2, GL_FLOAT, 0, (char *) NULL );
    } else {
        glVertexPointer( 3, GL_FLOAT, 0, m_vertexes );
        //glNormalPointer(GL_FLOAT, 0, m_normals );
        glTexCoordPointer( 2, GL_FLOAT, 0, m_texCoords );
    }

    glBindTexture(GL_TEXTURE_2D, m_texture);
    glDrawElements(GL_TRIANGLE_STRIP, m_numIndexes, GL_UNSIGNED_INT, m_indexes);

    glDisableClientState( GL_VERTEX_ARRAY );
    //glDisableClientState( GL_NORMAL_ARRAY );
    glDisableClientState( GL_TEXTURE_COORD_ARRAY );
}

void MapRendererWidget::generateTexCoords() {
    if(m_texCoords) delete m_texCoords;
    m_texCoords = new TexCoord[m_vertexNumber];
    for(int i = 0; i < m_heightmap.getHeight(); i++) {
        for(int j = 0; j < m_heightmap.getWidth(); j++) {
            m_texCoords[i*m_heightmap.getWidth()+j].u = (float)j/m_heightmap.getWidth();
            m_texCoords[i*m_heightmap.getWidth()+j].v = m_heightmap.getHeight() - (float)i/m_heightmap.getHeight();
        }
    }
}

void MapRendererWidget::generateIndexes() {
    m_numIndexes = (m_heightmap.getWidth() * 2) * (m_heightmap.getHeight() - 1) + (m_heightmap.getHeight() - 2);

    if(m_indexes) delete m_indexes;
    m_indexes = new unsigned int[m_numIndexes];

    int index = 0;
    for ( int z = 0; z < m_heightmap.getHeight() - 1; z++ ) {
        // Even rows move left to right, odd rows move right to left.
        if ( z % 2 == 0 ) {
            // Even row
            int x;
            for ( x = 0; x < m_heightmap.getWidth(); x++ ) {
                m_indexes[index++] = x + (z * m_heightmap.getWidth());
                m_indexes[index++] = x + (z * m_heightmap.getWidth()) + m_heightmap.getWidth();
            }
            // Insert degenerate vertex if this isn't the last row
            if ( z != m_heightmap.getHeight() - 2) {
                m_indexes[index++] = --x + (z * m_heightmap.getWidth());
            }
        } else {
            // Odd row
            int x;
            for ( x = m_heightmap.getWidth() - 1; x >= 0; x-- ) {
                m_indexes[index++] = x + (z * m_heightmap.getWidth());
                m_indexes[index++] = x + (z * m_heightmap.getWidth()) + m_heightmap.getWidth();
            }
            // Insert degenerate vertex if this isn't the last row
            if ( z != m_heightmap.getHeight() - 2) {
                m_indexes[index++] = ++x + (z * m_heightmap.getWidth());
            }
        }
    }
}

void MapRendererWidget::makeObject() {
    blockRerender = true;
    /*GLProgressDialog* progress = new GLProgressDialog(this);
    progress->setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
    progress->setMax(m_heightmap.getHeight());
    progress->updateProgress(0);
    progress->show();
    QApplication::processEvents();*/
    if(getGLExtensionFunctions().openGL15Supported()) {
        glGenBuffers( 1, &m_VBOVertices );
        glBindBuffer( GL_ARRAY_BUFFER, m_VBOVertices );
        glBufferData( GL_ARRAY_BUFFER, m_vertexNumber*sizeof(Vertex), m_vertexes, GL_STATIC_DRAW );
    }
    //computeNormals();
    //if(getGLExtensionFunctions().openGL15Supported()) {
    //GenBuffers( 1, &m_VBONormals );
    //BindBuffer( GL_ARRAY_BUFFER, m_VBONormals );
    //BufferData( GL_ARRAY_BUFFER, m_vertexNumber*sizeof(Vertex), m_normals, GL_STATIC_DRAW );
    //}
    generateTexCoords();
    if(getGLExtensionFunctions().openGL15Supported()) {
        glGenBuffers( 1, &m_VBOTexCoords );
        glBindBuffer( GL_ARRAY_BUFFER, m_VBOTexCoords );
        glBufferData( GL_ARRAY_BUFFER, m_vertexNumber*sizeof(TexCoord), m_texCoords, GL_STATIC_DRAW );
    }
    m_texture = bindTexture(QPixmap::fromImage(m_minimap), GL_TEXTURE_2D);
    generateIndexes();
    //QApplication::processEvents();
    //progress->hide();
    //delete progress;
    m_heightmap.free();
    blockRerender = false;
}


#define V(i,j) m_vertexes[(i)*m_heightmap.getWidth()+(j)]
#define N(i,j) m_normals[(i)*m_heightmap.getWidth()+(j)]

void MapRendererWidget::computeNormals() {
    if(m_normals) delete m_normals;
    Vertex* m_normals = new Vertex[m_vertexNumber];
    memset(m_normals, 0, m_vertexNumber*sizeof(Vertex));
    for(int i = 0; i < m_heightmap.getHeight(); i++) {
        for(int j = 0; j < m_heightmap.getWidth(); j++) {
            Vertex left, right, top, bottom, current;
            current = V(i,j);
            if(i > 0) left = V(i-1,j);
            if(j > 0) top = V(i,j-1);
            if(i < m_heightmap.getHeight() - 1) right = V(i+1,j);
            if(j < m_heightmap.getWidth() - 1) bottom = V(i,j+1);
            N(i,j).add(Vertex::getNormal(current, left, top));
            N(i,j).add(Vertex::getNormal(current, left, bottom));
            N(i,j).add(Vertex::getNormal(current, right, top));
            N(i,j).add(Vertex::getNormal(current, right, bottom));
            N(i,j).normalize();
        }
    }
}

void MapRendererWidget::setSource(QString mapName, QImage minimap, RawHeightMap heightmap) {
    if(currentMap == mapName) return;
    currentMap = mapName;
    m_minimap = minimap;
    m_heightmap.free();
    m_heightmap = heightmap;
	if(getGLExtensionFunctions().openGL15Supported()) {
		glDeleteBuffers(1, &m_VBOVertices);
		glDeleteBuffers(1, &m_VBONormals);
		glDeleteBuffers(1, &m_VBOTexCoords);
	}
    m_vertexNumber = heightmap.getWidth()*(heightmap.getHeight()-1)*2;
    if(m_vertexes) delete m_vertexes;
    m_vertexes = new Vertex[m_vertexNumber];
    for(int i = 0; i < heightmap.getHeight(); i++) {
        for(int j = 0; j < heightmap.getWidth(); j++) {
            unsigned short int value = heightmap.getData()[i*heightmap.getWidth()+j];
            int offset = i*heightmap.getWidth()+j;
            m_vertexes[offset].x = i * CELL_SIZE;
            m_vertexes[offset].y = j * CELL_SIZE;
            m_vertexes[offset].z = value/(float)MAX_SHORT*MAX_HEIGHT;
        }
    }
    compileObject = true;
    startRects.clear();
    if(hasFocus()) updateGL();
}


void MapRendererWidget::setXRotation(int angle) {
    normalizeAngle(&angle);
    if (angle != xRot) {
        xRot = angle;
        updateGL();
    }
}

void MapRendererWidget::setYRotation(int angle) {
    normalizeAngle(&angle);
    if (angle != yRot) {
        yRot = angle;
        updateGL();
    }
}

void MapRendererWidget::setZRotation(int angle) {
    normalizeAngle(&angle);
    if (angle != zRot) {
        zRot = angle;
        updateGL();
    }
}

void MapRendererWidget::normalizeAngle(int *angle) {
    while (*angle < 0)
        *angle += 360 * 16;
    while (*angle > 360 * 16)
        *angle -= 360 * 16;
}

void MapRendererWidget::wheelEvent ( QWheelEvent * event ) {
    float newZoom = lastZoom - event->delta() * 0.0005;
    if(newZoom > 1 || newZoom < 0.1) return;
    lastZoom = newZoom;
    resizeGL(width(), height());
    updateGL();
}

void MapRendererWidget::mousePressEvent(QMouseEvent *event) {
    lastPos = event->pos();
}

void MapRendererWidget::mouseMoveEvent(QMouseEvent *event) {
    int dx = event->x() - lastPos.x();
    int dy = event->y() - lastPos.y();

    if (event->buttons() & Qt::LeftButton) {
        //setXRotation(xRot + 8 * dy);
        setYRotation(yRot + 8 * dy);
    } else if (event->buttons() & Qt::RightButton) {
        //setXRotation(xRot + 8 * dy);
        setZRotation(zRot + 8 * dx);
    } else if (event->buttons() & Qt::MidButton) {
        this->dx += -dx * 0.2;
        this->dy += dy * 0.2;
        resizeGL(width(), height());
        updateGL();
    }
    lastPos = event->pos();
}


void MapRendererWidget::drawStartRecs() {
    m_withRects = m_minimap;
    QPainter p(&m_withRects);
    for(QMap<int, QRect>::const_iterator i = startRects.begin(); i != startRects.end(); i++) {
        QRect scaled = i.value();
        scaled.setWidth(scaled.width()/200.*m_withRects.width());
        scaled.setHeight(scaled.height()/200.*m_withRects.height());
        scaled.setX(scaled.x()/200.*m_withRects.width());
        scaled.setY(scaled.y()/200.*m_withRects.height());
        int alpha = 50;
        int width = 2;
        QColor red(Qt::red);
        QColor redFill(Qt::red);
        redFill.setAlpha(alpha);
        QColor green(Qt::green);
        QColor greenFill(Qt::green);
        greenFill.setAlpha(alpha);
        if(i.key() == myAlly) {
            p.setBrush(greenFill);
            p.setPen(QPen(green, width));
        } else {
            p.setBrush(redFill);
            p.setPen(QPen(red, width));
        }
        p.drawRect(scaled);
    }
    p.end();
    m_withRects.save("/home/lupus/tmp/texture.png");
    deleteTexture(m_texture);
    m_texture = bindTexture(QPixmap::fromImage(m_withRects), GL_TEXTURE_2D);
}

void MapRendererWidget::addStartRect(int ally, QRect r) {
    startRects[ally] = r;
    drawStartRecs();
    updateGL();
}

void MapRendererWidget::setMyAllyTeam(int n) {
    if(myAlly == n) return;
    myAlly = n;
    drawStartRecs();
    updateGL();
}




// Vertex

Vertex::Vertex() {
    this->x = x;
    this->y = y;
    this->z = z;
}

Vertex::Vertex(float x, float y, float z) {
    this->x = x;
    this->y = y;
    this->z = z;
}

void Vertex::setXYZ(float x, float y, float z) {
    this->x = x;
    this->y = y;
    this->z = z;
}

Vertex Vertex::getNormal(Vertex& v1, Vertex& v2) {
    Vertex n(v1.y*v2.z - v2.y*v1.z, v1.z*v2.x - v2.z*v1.x, v1.x*v2.y - v2.x*v1.y);
    n.normalize();
    return n;

}

Vertex Vertex::getNormal(Vertex& a, Vertex& b, Vertex& c) {
    Vertex n;
    Vertex v1, v2;
    v1.setXYZ(b.x - a.x, b.y - a.y, b.z - a.z);
    v2.setXYZ(c.x - a.x, c.y - a.y, c.z - a.z);
    n.setXYZ(v1.y*v2.z - v2.y*v1.z, v1.z*v2.x - v2.z*v1.x, v1.x*v2.y - v2.x*v1.y);
    n.normalize();
    return n;
}

void Vertex::normalize() {
    float Mn = sqrtf(powf(x, 2) + powf(y, 2) + powf(z, 2));
    x /= Mn;
    y /= Mn;
    z /= Mn;
}

void Vertex::add(Vertex v2) {
    x += v2.x;
    y += v2.y;
    z += v2.z;
}

void Vertex::sub(Vertex v2) {
    x -= v2.x;
    y -= v2.y;
    z -= v2.z;
}

